package com.vvv.designpattern.synchronizedpattern;

/**
 * @author vvv
 * @date 2023-10-17 08 55
 * discription 3个线程交替输出5次 abcabcabcabcabcabc
 */
public class AlternatingWaitAndNotify {
    public static void main(String[] args) {
        WaitAndNotify waitAndNotify = new WaitAndNotify(1, 5);
        new Thread(() -> {
            waitAndNotify.print("a", 1, 2);
        }, "t1").start();
        new Thread(() -> {
            waitAndNotify.print("b", 2, 3);
        }, "t2").start();
        new Thread(() -> {
            waitAndNotify.print("c", 3, 1);
        }, "t3").start();
    }
}
/**
 *输出    自己的标记   下一次标记
 * a        1       2
 * b        2       3
 * c        3       1
 */
class WaitAndNotify {
    // 等待标记
    private Integer flag;
    // 循环次数
    private Integer loopNumber;

    public void print(String output, int waitFlag, int nextFlag) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                // 不是自己的标记
                /**
                 * 这里对while的解释：
                 * 因为是异步的，假设在第一时间先 t2 拿到了锁 然后才是 t3 和 t1
                 *  t2 拿到了锁，发现不是自己的flag，于是wait，进入waitset，锁释放
                 *  t3 拿到了锁，发现不是自己的flag，于是wait，进入waitset，锁释放
                 *  t1 拿到了锁，发现是自己的flag，于是进行进行，修改flag = 2，notifyAll，锁释放，进入下一轮loop，然后从新争抢锁，进入entrySet
                 *  notifyAll 说明 t2 和 t3 从waitSet中转到entrySet中，重新争抢锁
                 *  假设t3先得到了锁（这就是虚假唤醒，因为应该是t2被唤醒的），如果这里是if的话，那么t3就不会再去看这个flag是不是自己的，
                 *  如果是while的话，t3再次看看 自己的waitFlag是不是flag，发现不是（flag == 2， t3的waitFlag == 3），所以继续wait，释放锁
                 *  假设t2拿到了锁，第二轮while发现是自己flag，所以跳出while修改flag = 3，notifyAll释放锁，进入下一轮loop，然后从新争抢锁，进入entrySet
                 *
                 *  总的来说，while是为了解决在同一个锁中的同一个休息室中唤醒某一个特定线程的办法
                 *  及满足while条件的才是真正唤醒的，没有满足while条件的要继续wait
                 */
                while (waitFlag != flag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 是自己的标记
                System.out.print(output);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }

    public WaitAndNotify(Integer flag, Integer loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
}
